package com.anjiplus.template.gaea.business.modules.inf.inf.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.anji.plus.gaea.constant.BaseOperationEnum;
import com.anji.plus.gaea.constant.GaeaConstant;
import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper;
import com.anji.plus.gaea.exception.BusinessException;
import com.anji.plus.gaea.inf.constant.InfConstant;
import com.anji.plus.gaea.inf.exception.InfException;
import com.anji.plus.gaea.inf.util.InfAssert;
import com.anji.plus.gaea.utils.GaeaAssert;
import com.anjiplus.template.gaea.business.modules.inf.app.dao.entity.GaeaInfAppRelation;
import com.anjiplus.template.gaea.business.modules.inf.app.dao.entity.InfAppEntity;
import com.anjiplus.template.gaea.business.modules.inf.app.service.GaeaInfAppRelationService;
import com.anjiplus.template.gaea.business.modules.inf.app.service.InfAppService;
import com.anjiplus.template.gaea.business.modules.inf.constant.InfCacheKey;
import com.anjiplus.template.gaea.business.modules.inf.constant.InfResCode;
import com.anjiplus.template.gaea.business.modules.inf.constant.InfStatusEnum;
import com.anjiplus.template.gaea.business.modules.inf.constant.RegularConstant;
import com.anjiplus.template.gaea.business.modules.inf.inf.controller.dto.InfDTO;
import com.anjiplus.template.gaea.business.modules.inf.inf.dao.InfMapper;
import com.anjiplus.template.gaea.business.modules.inf.inf.dao.entity.InfEntity;
import com.anjiplus.template.gaea.business.modules.inf.inf.service.InfService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 接口维护表(Inf)表服务实现类
 *
 * @author ultrajiaming
 * @since 2021-03-17 10:17:25
 */
@Service
@RefreshScope
public class InfServiceImpl implements InfService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Value("${inf.authorizated.database:}")
    private String authorizatedDatabase;

    @Autowired
    private InfMapper infMapper;

    @Autowired
    private GaeaInfAppRelationService gaeaInfAppRelationService;

    @Autowired
    private InfAppService infAppService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    private String databaseName;

    @Override
    public GaeaBaseMapper<InfEntity> getMapper() {
        return infMapper;
    }

    @Override
    public InfEntity getByName(String infName) {
        return infMapper.selectOne(Wrappers.<InfEntity>lambdaQuery().eq(InfEntity::getInfName, infName));
    }


    @Override
    public void processBeforeOperation(InfEntity entity, BaseOperationEnum operationEnum) throws BusinessException {
        switch (operationEnum) {
            case INSERT:
                if (entity != null) {
                    this.checkParam(entity);
                    entity.setStatus(InfStatusEnum.EDIT.getValue());
                }
                break;
            case UPDATE:
                if (entity != null) {
                    this.checkParam(entity);
                    InfEntity infEntity = this.getById(entity.getId());
                    GaeaAssert.notNull(infEntity, InfResCode.INF_NOT_EXIST);
                    // 已上线的不能编辑
                    if (InfStatusEnum.PUBLISHED.getValue() == infEntity.getStatus()) {
                        throw InfException.code(InfResCode.PUBLISHED_CANNOT_EDIT);
                    }

                    if (!infEntity.getInfName().equals(entity.getInfName())) {
                        throw InfException.code(InfResCode.INF_NAME_CANNOT_EDIT);
                    }
                    entity.setStatus(InfStatusEnum.EDIT.getValue());
                }
                break;
            case DELETE:
                if (entity != null) {
                    // 上线的接口不能删除
                    if (InfStatusEnum.PUBLISHED.getValue() == entity.getStatus()) {
                        throw InfException.code(InfResCode.PUBLISHED_CANNOT_DELETE);
                    }
                }
                break;
        }
    }

    @Override
    public void processAfterOperation(InfEntity entity, BaseOperationEnum operationEnum) throws BusinessException {
        switch (operationEnum) {
            case INSERT:
                if (entity != null) {
                }
                break;
            case UPDATE:
                if (entity != null) {
                }
                break;
            case DELETE:
                if (entity != null) {
                    // 删除接口授权的 缓存
                    String redisKey = InfCacheKey.AUTHORIZED_APP + entity.getInfName();
                    stringRedisTemplate.delete(redisKey);
                }
                break;
        }
    }

    private void checkParam(InfEntity infEntity) {
        this.checkSqlSentence(infEntity.getSqlSentence());
    }

    /**
     * SQL 语句校验
     **/
    private void checkSqlSentence(String sqlSentence) {
        if (StringUtils.isNotBlank(sqlSentence)) {
            String lowerSql = sqlSentence.toLowerCase().trim();
            // ${}
            GaeaAssert.isTrue(RegularConstant.STATIC_SQL_PATTERN.matcher(lowerSql).matches(),
                    InfResCode.SQL_SEQUENCE_ERROR_$);
            // ;
            GaeaAssert.isTrue(lowerSql.contains(";"), InfResCode.SQL_SEQUENCE_ERROR_SEMICOLON);
            // 注释
            GaeaAssert.isTrue(lowerSql.contains("--") || lowerSql.contains("/*") || lowerSql.contains("*/"), InfResCode.SQL_SEQUENCE_ERROR_NOTES);
            // 必须select开头
            GaeaAssert.isTrue(!lowerSql.startsWith("select"), InfResCode.SQL_SEQUENCE_SELECT_ERROR);
            // 不能出现的SQL关键字
            boolean excludeKeyWords = RegularConstant.SQL_EXCLUDE_KEYWORDS.stream()
                    .filter(regex -> regex.matcher(sqlSentence).matches()).findAny().isPresent();
            InfAssert.isTrue(excludeKeyWords, InfResCode.SQL_SEQUENCE_SELECT_ERROR);
            // 授权访问的数据库  默认只能访问当前数据库
            List<String> databaseList = new ArrayList<>();
            if (StringUtils.isNotBlank(authorizatedDatabase)) {
                databaseList.addAll(Arrays.asList(authorizatedDatabase.trim().toLowerCase().split(",")));
            }
            if (CollectionUtils.isEmpty(databaseList)) {
                if (StringUtils.isBlank(databaseName)) {
                    String url = "";
                    try {
                        url = sqlSessionFactory.openSession().getConnection().getMetaData().getURL();
                    } catch (SQLException throwables) {
                        throwables.printStackTrace();
                    }
                    if (url.contains("?")) {
                        String s = url.split("\\?")[0];
                        String substring = s.substring(s.lastIndexOf("/") + 1);
                        databaseName = substring.trim();
                    } else {
                        String substring = url.substring(url.lastIndexOf("/") + 1);
                        databaseName = substring.trim();
                    }
                }
                if (StringUtils.isNotBlank(databaseName)) {
                    databaseList.add(databaseName);
                }
            }
            if (!CollectionUtils.isEmpty(databaseList)) {
                Pattern patternDatabase = InfConstant.PATTERN_DATABASE_NAME;
                Matcher matcher = patternDatabase.matcher(lowerSql);
                while (matcher.find()) {
                    String databaseName = matcher.group(2);
                    if (StringUtils.isNotBlank(databaseName)) {
                        databaseName = databaseName.replaceAll("`", "");
                        InfAssert.isTrue(!databaseList.contains(databaseName), InfResCode.SQL_SEQUENCE_SELECT_ERROR);
                    }
                }
            }
        }
    }



    @Override
    @Transactional(rollbackFor = Exception.class)
    public void online(Long id) {
        logger.info("{}上线服务开始，参数：{}", this.getClass().getSimpleName(), id);
        GaeaAssert.notNull(id, InfResCode.ID_NOT_NULL);
        InfEntity infEntity = this.getById(id);
        GaeaAssert.notNull(infEntity, InfResCode.INF_NOT_EXIST);
        // 无需重复上线
        if (InfStatusEnum.PUBLISHED.getValue() == infEntity.getStatus()) {
            throw InfException.code(InfResCode.DUPLICATE_PUBLISHED);
        }
        // 审核通过才可以上线
        if (InfStatusEnum.EXAMINE.getValue() != infEntity.getStatus()) {
            throw InfException.code(InfResCode.ONLY_EXAMINE_CAN_PUBLISH);
        }
        // 上线
        infEntity.setStatus(InfStatusEnum.PUBLISHED.getValue());
        infMapper.updateById(infEntity);
        // TODO 处理缓存

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void offline(Long id) {
        logger.info("{}下线服务开始，参数：{}", this.getClass().getSimpleName(), id);
        GaeaAssert.notNull(id, InfResCode.ID_NOT_NULL);
        InfEntity infEntity = this.getById(id);
        GaeaAssert.notNull(infEntity, InfResCode.INF_NOT_EXIST);
        // 只有上线的才可以下线
        if (InfStatusEnum.PUBLISHED.getValue() != infEntity.getStatus()) {
            throw InfException.code(InfResCode.ONLY_PUBLISHED_CAN_OFFLINE);
        }
        // 下线
        infEntity.setStatus(InfStatusEnum.OFFLINE.getValue());
        infMapper.updateById(infEntity);
        // TODO 处理缓存

    }

    @Override
    public void examine(Long id) {
        logger.info("{}审核服务开始，参数：{}", this.getClass().getSimpleName(), id);
        GaeaAssert.notNull(id, InfResCode.ID_NOT_NULL);
        InfEntity infEntity = this.getById(id);
        GaeaAssert.notNull(infEntity, InfResCode.INF_NOT_EXIST);
        // 测试通过才可以审核
        if (InfStatusEnum.TEST_PASS.getValue() != infEntity.getStatus()) {
            throw InfException.code(InfResCode.ONLY_TESTED_CAN_EXAMINE);
        }
        // 审核通过
        infEntity.setStatus(InfStatusEnum.EXAMINE.getValue());
        infMapper.updateById(infEntity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void authorizeApps(String infName, String appIds) {
        if (StringUtils.isBlank(infName)) {
            logger.info("请求参数infName为空");
            return;
        }
        LambdaQueryWrapper<GaeaInfAppRelation> wrapper
                = Wrappers.<GaeaInfAppRelation>lambdaQuery().eq(GaeaInfAppRelation::getInfName, infName);
        List<GaeaInfAppRelation> oldInfAppList = gaeaInfAppRelationService.list(wrapper);
        if (!CollectionUtils.isEmpty(oldInfAppList)) {
            List<Long> deleteIdList = oldInfAppList.stream().map(infApp -> infApp.getId()).collect(Collectors.toList());
            // 删除接口授权的应用
            gaeaInfAppRelationService.deleteByIds(deleteIdList);
        }


        if (StringUtils.isNotBlank(appIds)) {
            InfEntity inf = getByName(infName);
            List<String> appIdList = Arrays.asList(appIds.split(GaeaConstant.SPLIT));
            // 获取appId、appName
            List<InfAppEntity> appList = infAppService.list(Wrappers.<InfAppEntity>lambdaQuery().in(InfAppEntity::getAppId, appIdList));
            Map<String, String> appMap = appList.stream().collect(Collectors.toMap(InfAppEntity::getAppId, InfAppEntity::getAppName));

            Map<String, GaeaInfAppRelation> oldInfAppMap = oldInfAppList.stream()
                    .collect(Collectors.toMap(GaeaInfAppRelation::getAppId, value -> value));

            List<GaeaInfAppRelation> infAppRelationList = appIdList.stream().map(appId -> {
                if (oldInfAppMap.containsKey(appId)) {
                    return oldInfAppMap.get(appId);
                } else {
                    GaeaInfAppRelation infAppR = new GaeaInfAppRelation();
                    infAppR.setInfName(infName);
                    infAppR.setAppId(appId);
                    infAppR.setAppName(appMap.get(appId));
                    infAppR.setInfNameName(inf == null ? null : inf.getInfNameName());
                    return infAppR;
                }
            }).collect(Collectors.toList());
            gaeaInfAppRelationService.insertBatch(infAppRelationList);
        }

        // 缓存
        // 删除redis授权应用
        String redisKey = InfCacheKey.AUTHORIZED_APP + infName;
        stringRedisTemplate.delete(redisKey);
        SetOperations<String, String> infAppOperations = stringRedisTemplate.opsForSet();
        if (StringUtils.isNotBlank(appIds)) {
            //缓存授权应用
            infAppOperations.add(redisKey, appIds.split(GaeaConstant.SPLIT));
        }
    }


    /**
     * 根据id 查询
     * @param id
     * @return
     */
    @Override
    public InfDTO queryById(Long id) {
        InfEntity infEntity = this.getById(id);
        GaeaAssert.notNull(infEntity, InfResCode.INF_NOT_EXIST);
        InfDTO infDTO = new InfDTO();
        BeanUtils.copyProperties(infEntity, infDTO);
        String requestParams = infDTO.getRequestParams();
        if (StringUtils.isNotBlank(requestParams)) {
            List<JSONObject> jsonObjects = JSON.parseArray(requestParams, JSONObject.class);
            List<JSONObject> result = new ArrayList<>();
            JSONObject infNameJsonObject = new JSONObject();
            infNameJsonObject.put(InfConstant.REQUEST_PARAM_KEY_NAME, InfConstant.EXECUTE_SQL_PARAM_INFNAME);
            infNameJsonObject.put(InfConstant.REQUEST_PARAM_KEY_TYPE, "String");
            infNameJsonObject.put(InfConstant.RESPONSE_PARAM_KEY_DESC, "接口标识");
            infNameJsonObject.put(InfConstant.PARAM_KEY_REQUIRE, "true");
            result.add(infNameJsonObject);
            JSONObject queryParamJsonObject = new JSONObject();
            queryParamJsonObject.put(InfConstant.REQUEST_PARAM_KEY_NAME, InfConstant.EXECUTE_SQL_PARAM_QUERYPARAM);
            queryParamJsonObject.put(InfConstant.REQUEST_PARAM_KEY_TYPE, "Object");
            queryParamJsonObject.put(InfConstant.RESPONSE_PARAM_KEY_DESC, "请求参数");
            queryParamJsonObject.put(InfConstant.PARAM_KEY_REQUIRE, "true");
            queryParamJsonObject.put(InfConstant.PARAM_KEY_CHILDREN, jsonObjects);
            result.add(queryParamJsonObject);
            String sqlParamResult = JSON.toJSONString(result);
//            System.out.println(sqlParamResult);
            infDTO.setSqlRequestParams(sqlParamResult);
        }
        return infDTO;
    }



}
